﻿using english_sentences.Models;
using english_sentences.Utilities;
using english_sentences.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using User = english_sentences.Models.User;

namespace english_sentences.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    [Produces("application/json")]
    public class AccountController : BaseApiController<AccountController>
    {
        private readonly SignInManager<User> _signInManager;
        private readonly UserManager<User> _userManager;
        private readonly RoleManager<Role> _roleManager;
        private readonly IConfiguration _configuration;

        public AccountController(UserManager<User> userManager, SignInManager<User> signInManager,
            RoleManager<Role> roleManager, ILogger<AccountController> logger, IConfiguration configuration
             ) : base(logger)
        {
            _signInManager = signInManager;
            _userManager = userManager;
            _roleManager = roleManager;
            _configuration = configuration;
        }

        [HttpPost("Register")]
        [AllowAnonymous]
        public async Task<ActionResult> Register([FromBody] RegisterViewModel model)
        {
            if (!ModelState.IsValid)
            {
                string s = ModelState.GetErrorMessages();

                return BadRequest(s);

            }

            try
            {
                var user = await _userManager.FindByEmailAsync(model.Email);

                if (user != null) return BadRequest("user existed.");

                user = new User
                {
                    Email = model.Email,
                    EmailConfirmed = true,
                    UserName = model.Email,
                    LockoutEnabled = true,

                };
                var registerResult = await _userManager.CreateAsync(user, model.Password);

                if (!registerResult.Succeeded)
                {
                    string s = string.Join(" | ", registerResult.Errors.Select(n => n.Description));

                    return BadRequest(s);
                }
                if (!await _roleManager.RoleExistsAsync("FreeUser"))
                {
                    Role r = new Role
                    {
                        Name = "FreeUser"
                    };

                    await _roleManager.CreateAsync(r);
                }
                await _userManager.AddToRoleAsync(user, "FreeUser");

                var message = "registered successfully";

                return Ok(new { success = true, message });
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex, ex.Message);
                throw new HttpResponseException(500);
            }


        }

        [HttpPost("Login")]
        [AllowAnonymous]
        public async Task<ActionResult> Login(LoginViewModel model)
        {
            try
            {
                string errorMessage = "invalid user or password";

                if (!ModelState.IsValid)
                {
                    return BadRequest(errorMessage);
                }
                var user = await _userManager.FindByEmailAsync(model.Email);

                if (user == null)
                {
                    return BadRequest("user does not exist");
                }
                if (await _userManager.IsLockedOutAsync(user)) return BadRequest("user was locked.");

                var result = await _signInManager.PasswordSignInAsync(user, model.Password, true, true);

                if (!result.Succeeded) return BadRequest(errorMessage);

                var token = await GenerateToken(user);

                user.RefreshToken = token.RefreshToken;

                user.RefreshTokenExpiryTime = DateTime.Now.AddDays(30);

                await _userManager.UpdateAsync(user);

                return Ok(token);
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex, ex.Message);

                throw new HttpResponseException(500);
            }
        }

        [HttpPost("Logout")]
        public async Task<ActionResult> Logout(TokenViewModel model)
        {
            try
            {
                string errorMessage = "invalid request";
                var principal = this.GetPrincipalFromExpiredToken(model?.AccessToken ?? "");

                var userName = principal.Identity?.Name;

                var user = _userManager.Users.FirstOrDefault(n => n.UserName == userName);

                if (user == null)
                {
                    return NotFound(errorMessage);
                }
                user.RefreshToken = "";

                user.RefreshTokenExpiryTime = DateTime.Now.AddSeconds(-1);

                await _userManager.UpdateAsync(user);
                await _signInManager.SignOutAsync();
                return Ok();
            }
            catch (Exception ex)
            {
                this._logger.LogError(ex, ex.Message);

                throw new HttpResponseException(500);

            }
        }

        [HttpPost("RefreshToken")]
        public async Task<ActionResult> RefreshToken(TokenViewModel model)
        {
            try
            {
                string errorMessage = "invalid request";

                if (!ModelState.IsValid)
                {
                    return BadRequest(errorMessage);
                }

                var principal = this.GetPrincipalFromExpiredToken(model?.AccessToken ?? "");

                var userName = principal.Identity?.Name;

                var user = _userManager.Users.FirstOrDefault(n => n.UserName == userName);

                if (user == null)
                {
                    return BadRequest(errorMessage);
                }
            
                
                if (user is null || user.RefreshToken != model.RefreshToken || user.RefreshTokenExpiryTime <= DateTime.Now)
                {
                    return Forbid("Bearer");
                }

                var newToken = await this.GenerateToken(user);

                user.RefreshToken = newToken.RefreshToken;

                user.RefreshTokenExpiryTime = DateTime.Now.AddDays(30);

                await _userManager.UpdateAsync(user);

                return Ok(newToken);

            }
            catch (Exception ex)
            {
                this._logger.LogError(ex, ex.Message);

                throw new HttpResponseException(500);


            }
        }


        private async Task<TokenViewModel> GenerateToken(User user)
        {
            var claims = new List<Claim> {
                new Claim (JwtRegisteredClaimNames.Sub, user.UserName),
                new Claim (JwtRegisteredClaimNames.Jti, Guid.NewGuid ().ToString ()),
                new Claim (ClaimTypes.Sid, user.Id.ToString ()),
                new Claim (ClaimTypes.Name, user.UserName),

            };
            var roles = await _userManager.GetRolesAsync(user);

            foreach (var role in roles)
            {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Authentication:JwtKey"]));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            var expires = DateTime.Now.AddDays(Convert.ToDouble(_configuration["Authentication:JwtExpireDays"]));

            var token = new JwtSecurityToken(_configuration["Authentication:JwtIssuer"], _configuration["Authentication:JwtAudience"], claims, expires: expires, signingCredentials: creds);

            var refreshToken = this.GenerateRefreshToken();
            return new TokenViewModel
            {
                RefreshToken = refreshToken,
                AccessToken = new JwtSecurityTokenHandler().WriteToken(token),
            };
        }

        private string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
        }
        private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
        {
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
                ValidateIssuer = false,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Authentication:JwtKey"])),
                ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
            };
            var tokenHandler = new JwtSecurityTokenHandler();
            SecurityToken securityToken;
            var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
            var jwtSecurityToken = securityToken as JwtSecurityToken;
            if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
                throw new SecurityTokenException("Invalid token");
            return principal;
        }
    }
}
